﻿
using System;
using IOP.Extension.Convert;

namespace IOP.Protocols.Network.IP
{
    public struct IPV4
    {
        /// <summary>
        /// 默认长度
        /// </summary>
        public const int DefaultLength = 20;
        /// <summary>
        /// 版本
        /// </summary>
        public const int Version = 4;
        /// <summary>
        /// IP包中效验和偏移
        /// </summary>
        public const int ChecksumOffest = 10;
        /// <summary>
        /// 版本号
        /// </summary>
        public byte IPVersion;
        /// <summary>
        /// 头部长度
        /// </summary>
        public byte IPHeaderLength;
        /// <summary>
        /// 差分服务代码点
        /// </summary>
        public DSCP DSCP;
        /// <summary>
        /// 显式拥塞通知
        /// </summary>
        public ECN ECN;
        /// <summary>
        /// 数据包总长度
        /// </summary>
        public ushort IPTotalLength;
        /// <summary>
        /// 标识 
        /// </summary>
        public ushort IPIdentity;

        /// <summary>
        /// 不要分片
        /// </summary>
        public bool DonTFragment;
        /// <summary>
        /// 更多分片
        /// </summary>
        public bool MoreFragment;

        /// <summary>
        /// 偏移
        /// </summary>
        public ushort IPOffset;
        /// <summary>
        /// 生存时间 TTL
        /// </summary>
        public byte IPTtl;
        /// <summary>
        /// 协议类型
        /// </summary>
        public IPProtocolType IPProtocol;
        /// <summary>
        /// 16位IP首部校验和  
        /// </summary>
        public ushort IPChecksum;
        /// <summary>
        /// 32位源IP地址   
        /// </summary>
        public uint IPSrcaddr;
        /// <summary>
        /// 32位目的IP地址   
        /// </summary>
        public uint IPDestaddr;
        /// <summary>
        /// 其他选项
        /// </summary>
        public byte[] IPOptions;

        /// <summary>
        /// 对现有结构体进行编码
        /// </summary>
        /// <returns></returns>
        public Span<byte> Encode()
        {
            var length = IPOptions == null ? DefaultLength : DefaultLength + IPOptions.Length;
            Span<byte> span = stackalloc byte[length];

            int index = 0;
            IPHeaderLength = (byte)(IPOptions == null ? DefaultLength : DefaultLength + IPOptions.Length);
            IPVersion = Version;

            span[index++] = (byte)(IPVersion << 4 | IPHeaderLength / 4);
            span[index++] = (byte)((byte)DSCP | (byte)ECN);

            IPTotalLength.WriteBytes(ref span, ref index);

            IPIdentity.WriteBytes(ref span, ref index);

            ushort offest = (ushort)(CreateFlags() | (MoreFragment ? IPOffset : 0));
            offest.WriteBytes(ref span, ref index);

            span[index++] = IPTtl;

            span[index++] = (byte)IPProtocol;

            IPChecksum.WriteBytes(ref span, ref index);

            IPSrcaddr.WriteBytes(ref span, ref index);

            IPDestaddr.WriteBytes(ref span, ref index);

            if (IPOptions != null) IPOptions.CopyTo(span[index..(span.Length - 1)]);

            Checksum(ref span);

            Span<byte> result = new Span<byte>(new byte[length]);
            span.CopyTo(result);
            return result;
        }

        /// <summary>
        /// 执行效验并将结果写入IP报文头效验和所在地址
        /// </summary>
        /// <param name="array"></param>
        public void Checksum(ref Span<byte> array)
        {
            ChecksumToZero(ref array, 0);
            ushort result = 0;
            int acc = 0;
            int len = array.Length;
            int index = 0;
            while(len > 1)
            {
                acc += (array[index++] << 8 | array[index++]);
                len -= 2;
            }
            if(len > 0)
            {
                acc = array[index++] << 8;
            }
            acc = (acc >> 16) + (acc & 0x0000ffff);
            if ((acc & 0xffff0000) != 0) acc = (acc >> 16) + (acc & 0x0000ffff);
            result = (ushort)(~((ushort)acc));
            int offest = ChecksumOffest;
            result.WriteBytes(ref array, ref offest);
        }

        /// <summary>
        /// 将报文中效验和置零
        /// </summary>
        /// <param name="array"></param>
        /// <param name="ipStartIndex">ip报文首地址</param>
        public void ChecksumToZero(ref Span<byte> array, int ipStartIndex)
        {
            array[ipStartIndex + ChecksumOffest] = 0;
            array[ipStartIndex + ChecksumOffest + 1] = 0;
        }

        private ushort CreateFlags()
        {
            int d = DonTFragment ? 1 << 14 : 0;
            int m = MoreFragment ? 1 << 13 : 0;
            return (ushort)(d | m);
        }
    }
}
